//||=========================================================================||
//||                                                                         ||
//||                          MandelbrotCounter.cpp                          ||
//||                                                                         ||
//||                           by the_best_goose))                           ||
//||                                                                         ||
//||=========================================================================||

#include <iostream>
#include <fstream>
#include <assert.h>
#include <algorithm>
#include <QFile>
#include "MandelbrotCounter.h"
#include "MainWindow.h"
#include "FastComplex.cpp"

//-----------------------------------------------------------------------------

MandelbrotCounter::MandelbrotCounter(const MainWindow* pwind,
                                     const QWidget* pmand_pic):
SimpleMandelbrotCounter(pwind, pmand_pic),
mapping_("Z^2 + C"),
file_name_(""),
is_valid_(false),
file_(),
lib_(),
pcount_func_(NULL),
t_(),
has_t_(false),
iter_points_count_(0),
iter_points_arr_(NULL),
has_julia_(false),
c_()
{
    assert(pwind);

    connect(pmand_pic, SIGNAL(MappingChanged(string)),
            this, SLOT(SetMapping(string)));
    connect(pmand_pic, SIGNAL(TReChanged(float)), this, SLOT(SetTRe(float)));
    connect(pmand_pic, SIGNAL(TImChanged(float)), this, SLOT(SetTIm(float)));

    connect(pmand_pic, SIGNAL(IterPointsChanged(QVector<QPointF>)),
            this, SLOT(SetIterPoints(QVector<QPointF>)));
}

//-----------------------------------------------------------------------------

MandelbrotCounter::~MandelbrotCounter()
{
    break_ = true;
    wait();
    file_.close();
    lib_.unload();
}


//-----------------------------------------------------------------------------

void MandelbrotCounter::run()
{
    assert(is_valid_);
    assert(pcount_func_);
    assert(pixels_);

    pcount_func_(FastComplex(t_.x(), t_.y()), iter_points_arr_,
                 iter_points_count_, &break_, (const int*) index_array_,
                 iters_, px_size_.width(), px_size_.height(), (int*) pixels_,
                 graph_rect_.x(), graph_rect_.y(), graph_rect_.width(),
                 graph_rect_.height(), x_before_, x_after_, y_trans_);
    emit Counted();
}

//-----------------------------------------------------------------------------

void MandelbrotCounter::SetMapping(string mapping)
{
    string::iterator end = std::remove(mapping.begin(), mapping.end(), ' ');
    mapping.resize(end - mapping.begin());

    if(!IsValid(mapping))
        return;
    
    if(mapping_ == mapping)
        return;
    break_ = true;
    wait();
    break_ = false;
    file_.close();
    lib_.unload();
    mapping_ = mapping;
    GenLibName();
    if(!QFile::exists((string("lib") + file_name_.c_str() +
    		           string(".so")).c_str()))
    {
        GenerateFile();
        CompileFile();
    }

    LoadLibrary();
    is_valid_ = true;
}

//-----------------------------------------------------------------------------

void MandelbrotCounter::GenerateFile()
{
    string to_append
            ("\nvoid Iterate(FastComplex& z, FastComplex& c,"
             "FastComplex& t){\n    z = ");
    to_append += GenFormula();
    to_append += string(";\n}\n\n");
    QFile::remove("gen/LastGeneratedCounter.cpp");
    QFile::copy ("gen/CounterTemplate.cpp", "gen/LastGeneratedCounter.cpp");
    QFile gen("gen/LastGeneratedCounter.cpp");
    assert(gen.open(QIODevice::Append));
    gen.write(to_append.c_str());
    gen.close();
}

//-----------------------------------------------------------------------------

void MandelbrotCounter::CompileFile()
{
    string for_call = "g++ -fpic -c gen/LastGeneratedCounter.cpp "
                      "-o gen/LastGeneratedCounter.o; g++ -shared -o \"gen/lib";
    for_call += file_name_;
    for_call += ".so\" gen/LastGeneratedCounter.o\n";
    system(for_call.c_str());
    assert(QLibrary::isLibrary((string("gen/lib") + file_name_ + string(".so")).
                                                                     c_str()));
}


//-----------------------------------------------------------------------------

void MandelbrotCounter::LoadLibrary()
{
    lib_.unload();
    lib_.setFileName((string("gen/lib") + file_name_.c_str() + string(".so")).
     		                                                          c_str());
    lib_.load();
    assert(lib_.isLoaded());
    pcount_func_ = (CountFunc) lib_.resolve("MandelbrotCounter");
    assert(pcount_func_);
}

//-----------------------------------------------------------------------------

void MandelbrotCounter::GenLibName()
{
    file_name_ = mapping_;
    for(unsigned int i = 0; i < file_name_.size(); ++i)
        if(file_name_[i] == '*')
            file_name_[i] = 'x';
        else if(file_name_[i] == '/')
            file_name_[i] = '%';
}

//-----------------------------------------------------------------------------

string MandelbrotCounter::GenFormula()
{
    string ans;
    for(int i = 0; i < (int) mapping_.size(); ++i)
        if(mapping_[i] == '^')
        {
            assert (i >= 1);
            int j = 0;
            if(mapping_[i - 1] == ')')
                for(j = 0; mapping_[i - j - 1] != '('; ++j);
            else
            {
                for(j = 0;  i - j - 1 >= 0 &&
                            !(mapping_[i - j - 1] == '+' ||
                              mapping_[i - j - 1] == '-' ||
                              mapping_[i - j - 1] == '*' ||
                              mapping_[i - j - 1] == '/'); ++j);
                j--;
            }
            ans.resize(ans.size() - j - 1);
            ans += "pow(";
            for(int k = i - j - 1; k < i; ++k)
                ans += mapping_[k];
            ans += ',';
            i++;
            if(mapping_[i] == '(')
                for(j = 0; mapping_[i + j] != ')'; ++j);
            else
            {
                for(j = 0; i + j < (int) mapping_.size() &&
                      ('0' <= mapping_[i + j] && mapping_[i + j] <= '9'); ++j);
            }
            for(int k = i; k < i + j; ++k)
                ans += mapping_[k];
            ans += ')';
            i += j - 1;
        }
        else
            ans += mapping_[i];
    return ans;
    }

//-----------------------------------------------------------------------------

bool MandelbrotCounter::IsValid(string mapping)
{
    string::iterator end = std::remove(mapping.begin(), mapping.end(), ' ');
    mapping.resize(end - mapping.begin());

    int open_count = 0;
    for(unsigned int i = 0; i < mapping.size(); ++i)
        if(mapping[i] == '(')
            open_count++;
        else if(mapping[i] == ')')
        {
            open_count--;
            if(open_count < 0)
                return false;
        }

    if(open_count != 0)
        return false;

    end = std::remove(mapping.begin(), mapping.end(), '(');
    mapping.resize(end - mapping.begin());
    end = std::remove(mapping.begin(), mapping.end(), ')');
    mapping.resize(end - mapping.begin());

    for(unsigned int i = 0; i < mapping.size(); ++i)
        if(mapping[i] == 'Z' || mapping[i] == 'C' ||
           mapping[i] == 'I' || mapping[i] == 'T' ||
           mapping[i] == 'i' || mapping[i] == 'c' ||
           mapping[i] == 'z' || mapping[i] == 't')
            mapping[i] = 'x';

    assert(mapping.size() >= 1);
    unsigned int i = 0;

    if(('0' <= mapping[0] && mapping[0] <= '9') || (mapping[0] == 'x'))
        while(mapping[i] != '\n' && mapping[i] != '^' &&
              mapping[i] != '+'  && mapping[i] != '-' &&
              mapping[i] != '*'  && mapping[i] != '/')
        {
            ++i;
            if(mapping[i] == 'x')
                return false;
        }
    else if(mapping[0] != '-')
        return false;

    for(i = 1; i < mapping.size(); ++i)
        if(mapping[i] == '+' || mapping[i] == '-' ||
           mapping[i] == '*' || mapping[i] == '/' ||
           mapping[i] == '^')
        {
            i++;
            if(('0' <= mapping[0] && mapping[0] <= '9') || (mapping[0] == 'x'))
                while(mapping[i] != '\0' && mapping[i] != '^' &&
                      mapping[i] != '+'  && mapping[i] != '-' &&
                      mapping[i] != '*'  && mapping[i] != '/')
                {
                    ++i;
                    if(mapping[i] == 'x')
                        return false;
                }
            else
                return false;
        }
    return true;
}

//-----------------------------------------------------------------------------

void MandelbrotCounter::SetTRe(float re)
{
    break_ = true;
    wait();
    break_ = false;
    t_ = QPointF(re, t_.y());
}

//-----------------------------------------------------------------------------

void MandelbrotCounter::SetTIm(float im)
{
    break_ = true;
    wait();
    break_ = false;
    t_ = QPointF(t_.x(), im);
}

//-----------------------------------------------------------------------------

void MandelbrotCounter::SetIterPoints(QVector<QPointF> iter_points)
{
    if(iter_points_arr_)
        delete[] iter_points_arr_;
    iter_points_count_ = iter_points.size();
    iter_points_arr_ = new FastComplex[iter_points_count_];
    for ( int i = 0; i < iter_points_count_; ++i)
    {
        iter_points_arr_[i].re = iter_points[i].x();
        iter_points_arr_[i].im = iter_points[i].y();
    }
    emit NeedRecount();
}
